package at.ptasat;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import at.ptasat.solver.AsyncSolverFactory;
import at.ptasat.solver.process.AsyncProcessSolverFactory;
import at.ptasat.solver.thread.AsyncThreadSolverFactory;

public final class Configuration {
	private static final String[] DEFAULT_ARGUMENTS = { "--queue=default", "--queue=random", "--queue=inverting", "--queue=static-occurences", "--queue=dynamic-occurences:5",
			"--queue=dynamic-occurences:1", "--queue=dynamic-occurences:25" };
	private static final String DEFAULT_REST_ARGUMENT = "--queue=random";
	private static final ArgumentConfig[] DEFAULT_ARGUMENT_CONFIGS;

	static {
		List<ArgumentConfig> defaultArgumentConfigs = new ArrayList<ArgumentConfig>();
		final int n = Runtime.getRuntime().availableProcessors();
		for (int i = 0; i < n; i++) {
			String argument;
			if (i < DEFAULT_ARGUMENTS.length) {
				argument = DEFAULT_ARGUMENTS[i];
			} else {
				argument = DEFAULT_REST_ARGUMENT;
			}
			defaultArgumentConfigs.add(new ArgumentConfig("1:\"" + argument + "\""));
		}
		DEFAULT_ARGUMENT_CONFIGS = defaultArgumentConfigs.toArray(new ArgumentConfig[defaultArgumentConfigs.size()]);
	}

	public enum PipeMode {
		IMMEDIATE("immediate"), LATE_ON_SELF_TERMINATION("late"), SILENT("silent");

		private final String representation;

		private PipeMode(String representation) {
			this.representation = representation;
		}

		public static PipeMode parse(String representation) {
			for (PipeMode value : values()) {
				if (value.representation.equals(representation)) {
					return value;
				}
			}
			return null;
		}

		@Override
		public String toString() {
			return representation;
		}
	}

	private InputStream in;
	private OutputStream out;
	private OutputStream err;
	private File file;
	private AsyncSolverFactory factory;
	private final List<ArgumentConfig> argumentConfigs;
	private boolean isDefaultArgumentConfigs;
	private List<String> globalArguments;
	private Random random;
	private int max;
	private PipeMode outPipeMode, errPipeMode;
	private boolean isVerboseConfig, isVerboseStatus, isVerboseWinner, isVerboseWorkers;

	{
		this.in = System.in;
		this.out = System.out;
		this.err = System.err;
		file = null;
		factory = new AsyncThreadSolverFactory();
		argumentConfigs = new ArrayList<ArgumentConfig>();
		for (ArgumentConfig config : DEFAULT_ARGUMENT_CONFIGS) {
			argumentConfigs.add(config);
		}
		isDefaultArgumentConfigs = true;
		globalArguments = new ArrayList<String>();
		random = new Random();
		max = Runtime.getRuntime().availableProcessors();
		outPipeMode = PipeMode.LATE_ON_SELF_TERMINATION;
		errPipeMode = PipeMode.LATE_ON_SELF_TERMINATION;
		isVerboseConfig = false;
		isVerboseStatus = false;
		isVerboseWinner = false;
		isVerboseWorkers = false;
	}

	public static Configuration get(String[] args) {
		Configuration config = new Configuration();
		for (int i = 0; i < args.length; i++) {
			String arg = args[i];
			if (arg.length() == 0) {
				continue;
			}
			if (arg.startsWith("-")) {
				if (arg.equals("-t") || arg.equals("--threads")) {
					config.setFactory(new AsyncThreadSolverFactory());
				} else if (arg.equals("-p") || arg.equals("--processes")) {
					config.setFactory(new AsyncProcessSolverFactory());
				} else if (arg.startsWith("-w")) {
					config.addArgumentConfig(new ArgumentConfig(arg.substring("-w".length())));
				} else if (arg.startsWith("--worker")) {
					config.addArgumentConfig(new ArgumentConfig(arg.substring("--worker".length())));
				} else {
					String name = arg.substring(0, arg.indexOf('='));
					String value = arg.substring(arg.indexOf('=') + 1, arg.length());
					if (name.equals("-v") || name.equals("--verbose")) {
						for (String vv : value.split(",")) {
							if (vv.equals("status")) {
								config.setVerboseStatus(true);
							} else if (vv.equals("winner")) {
								config.setVerboseWinner(true);
							} else if (vv.equals("workers")) {
								config.setVerboseWorkers(true);
							} else if (vv.equals("config")) {
								config.setVerboseConfig(true);
							} else {
								throw new IllegalArgumentException("Unknown verbose value: " + vv);
							}
						}
					} else if (name.equals("-g") || name.equals("--global")) {
						config.addGlobalArguments(value);
					} else if (name.equals("-m") || name.equals("--max")) {
						int max = Integer.valueOf(value);
						if (max < 0) {
							throw new IllegalArgumentException("max must be zero or greater but was " + max + "!");
						}
						config.setMax(max);
					} else if (name.equals("-o") || name.equals("--out-pipe-mode")) {
						config.setOutPipeMode(PipeMode.parse(value));
					} else if (name.equals("-e") || name.equals("--err-pipe-mode")) {
						config.setErrPipeMode(PipeMode.parse(value));
					} else {
						throw new IllegalArgumentException("Unknown property name: " + name);
					}
				}
			} else {
				config.setIn(new File(arg));
				break;
			}
		}
		return config;
	}

	public boolean isInFile() {
		return file != null;
	}

	public InputStream getIn() throws IOException {
		return in != null ? in : (in = new FileInputStream(file));
	}

	public OutputStream getOut() {
		return out;
	}

	public OutputStream getErr() {
		return err;
	}

	public File getInFile() {
		return file;
	}

	public AsyncSolverFactory getFactory() {
		return factory;
	}

	public ArgumentConfig[] getArgumentConfigs() {
		return argumentConfigs.toArray(new ArgumentConfig[argumentConfigs.size()]);
	}

	public String[] getGlobalArguments() {
		return globalArguments.toArray(new String[globalArguments.size()]);
	}

	public Random getRandom() {
		return random;
	}

	public int getMax() {
		return max;
	}

	public PipeMode getOutPipeMode() {
		return outPipeMode;
	}

	public PipeMode getErrPipeMode() {
		return errPipeMode;
	}

	public boolean isVerboseConfig() {
		return isVerboseConfig;
	}

	public boolean isVerboseStatus() {
		return isVerboseStatus;
	}

	public boolean isVerboseWinner() {
		return isVerboseWinner;
	}

	public boolean isVerboseWorker() {
		return isVerboseWorkers;
	}

	public void setIn(InputStream in) {
		assert in != null;
		this.in = in;
		this.file = null;
	}

	public void setIn(File file) {
		assert file != null;
		this.file = file;
		this.in = null;
	}

	public void setOut(OutputStream out) {
		assert out != null;
		this.out = out;
	}

	public void setErr(OutputStream err) {
		assert err != null;
		this.err = err;
	}

	public void setFactory(AsyncSolverFactory factory) {
		assert factory != null;
		this.factory = factory;
	}

	public void addArgumentConfig(ArgumentConfig config) {
		assert config != null;
		if (isDefaultArgumentConfigs) {
			argumentConfigs.clear();
			isDefaultArgumentConfigs = false;
		}
		argumentConfigs.add(config);
	}

	public void addGlobalArguments(String globalArgument) {
		assert globalArguments != null;
		globalArguments.add(globalArgument);
	}

	public void setRandom(Random random) {
		assert random != null;
		this.random = random;
	}

	public void setMax(int max) {
		if (max <= 0) {
			throw new IllegalArgumentException("Max must be greater than zero!");
		}
		this.max = max;
	}

	public void setOutPipeMode(PipeMode mode) {
		assert mode != null;
		if (mode == PipeMode.SILENT) {
			throw new IllegalArgumentException("out-pipe-mode mustn't be silent!");
		}
		outPipeMode = mode;
	}

	public void setErrPipeMode(PipeMode mode) {
		assert mode != null;
		errPipeMode = mode;
	}

	public void setVerboseConfig(boolean verboseConfig) {
		this.isVerboseConfig = verboseConfig;
	}

	public void setVerboseStatus(boolean verboseStatus) {
		this.isVerboseStatus = verboseStatus;
	}

	public void setVerboseWinner(boolean verboseWinner) {
		this.isVerboseWinner = verboseWinner;
	}

	public void setVerboseWorkers(boolean verboseWorkers) {
		this.isVerboseWorkers = verboseWorkers;
	}

	public static final class ArgumentConfig {
		public final int nInstances;
		public final String argument;

		public ArgumentConfig(String arg) {
			nInstances = Integer.valueOf(arg.substring(0, arg.indexOf(':')));
			String argument = arg.substring(arg.indexOf(':') + 1).replace('"', ' ').trim();
			this.argument = argument;
		}

		@Override
		public String toString() {
			return String.format("%d:\"%s\"", nInstances, argument);
		}
	}

	@Override
	public String toString() {
		StringBuilder string = new StringBuilder();
		string.append(String.format("--verbose=%s,%s,%s,%s ", isVerboseConfig ? "config" : "", isVerboseStatus ? "status" : "", isVerboseWinner ? "winner" : "", isVerboseWorkers ? "workers" : ""));
		string.append(String.format("--%s ", factory.getName()));
		for (ArgumentConfig argumentConfig : argumentConfigs) {
			string.append(String.format("--worker%s ", argumentConfig));
		}
		string.append(String.format("--global=%s ", globalArguments));
		string.append(String.format("--max=%d ", max));
		if (file != null) {
			string.append(file.getAbsolutePath());
		}
		return string.toString().trim();
	}
}
